#include "ControlRenderer.hpp"
#include "../Resource/Mesh/MeshIO/MeshIO.hpp"

namespace zzz
{
bool ControlRenderer::Draw()
{
  if (mesh_)
  {

    glMatrixMode(GL_MODELVIEW);
    glPushMatrix();
    glLoadIdentity();
    camera_.ApplyGL();
    
    objArcBall_.ApplyGL();

    if (showObj_)
    {
      glEnable(GL_LIGHTING);
      glColor3f(0.26,0.73,0.5);
      mesh_->Draw();
      glDisable(GL_LIGHTING);
    }

    if (showCpt_)
    {
      ZRM->Get<Shader*>("ColorShader")->Begin();
      for (zuint i=0; i<ControlPoints_.size(); i++)
      {
        glPushMatrix();
        glTranslatef(ControlPoints_[i].v[0],ControlPoints_[i].v[1],ControlPoints_[i].v[2]);
        glColor3f(0,0,1);
        gluSphere(quadric,0.02*mesh_->AABB_.Diff().Max(),6,5);
        glPopMatrix();
      }
      if (overpoint_!=-1)
      {
        if (keyDown_==ZZZKEY_S)
        {
          glPushMatrix();
          glTranslatef(mesh_->pos_[overpoint_][0],mesh_->pos_[overpoint_][1],mesh_->pos_[overpoint_][2]);
          if (find(ControlPoints_.begin(),ControlPoints_.end(),mesh_->pos_[overpoint_])==ControlPoints_.end())
            glColor3f(1,0,0);
          else
            glColor3f(0,1,0);
          gluSphere(quadric,0.021*mesh_->AABB_.Diff().Max(),6,5);
          glPopMatrix();
        }
        else
        {
          glPushMatrix();
          glTranslatef(ControlPoints_[overpoint_].v[0],ControlPoints_[overpoint_].v[1],ControlPoints_[overpoint_].v[2]);
          glColor3f(0,1,0);
          gluSphere(quadric,0.021*mesh_->AABB_.Diff().Max(),6,5);
          glPopMatrix();
        }
      }
      Shader::End();
    }

    glPopMatrix();

    return true;
  }
  else
    return OneObjRenderer::Draw();
}

void ControlRenderer::OnLButtonDown(unsigned int nFlags,int x,int y)
{
  switch (keyDown_)
  {
  case ZZZKEY_F:
    overpoint_=FindNearestSelected(x,y);
    if (overpoint_!=-1)
    {
      selectedpoint_=overpoint_;

      glMatrixMode(GL_MODELVIEW);
      glPushMatrix();
      glLoadIdentity();
      camera_.ApplyGL();
      
      objArcBall_.ApplyGL();
      Cartesian3DCoordf point=ControlPoints_[selectedpoint_];

      GLdouble modelMatrix[16], projMatrix[16];
      GLint viewport[4];
      glGetDoublev(GL_MODELVIEW_MATRIX,modelMatrix);
      glGetDoublev(GL_PROJECTION_MATRIX,projMatrix);
      glGetIntegerv(GL_VIEWPORT,viewport);

      double winx,winy,winz;
      gluProject(point.v[0],point.v[1],point.v[2],modelMatrix,projMatrix,viewport,&winx,&winy,&winz);
      selectedx_=winx-x;
      selectedy_=winy-(height_-y);
      selectedz_=winz;

      glPopMatrix();
    }
    leftButton_=true;
    break;
  case ZZZKEY_NOKEY:
    OneObjRenderer::OnLButtonDown(nFlags,x,y);
  }

}

void ControlRenderer::OnKeyDown(unsigned int nChar, unsigned int nRepCnt, unsigned int nFlags)
{
  if (keyDown_==ZZZKEY_NOKEY)
  {
    keyDown_=nChar;
    if (nChar==ZZZKEY_SPACE) showCpt_=!showCpt_;
    Redraw();
  }
}

ControlRenderer::ControlRenderer()
{
  keyDown_=ZZZKEY_NOKEY;
  fbo_=NULL;
  target_=NULL;
  depth_=NULL;
  overpoint_=-1;
  quadric=gluNewQuadric();
  showCpt_=true;
  showObj_=true;
  mesh_=NULL;

  leftButton_ = false;
  middleButton_ = false;
}

void ControlRenderer::OnLButtonUp(unsigned int nFlags,int x,int y)
{
  switch (keyDown_)
  {
  case ZZZKEY_S:
    if (overpoint_!=-1)
    {
      vector<Cartesian3DCoordf>::iterator vui=find(ControlPoints_.begin(),ControlPoints_.end(),mesh_->pos_[overpoint_]);
      if (vui==ControlPoints_.end())
      {
        ControlPoints_.push_back(mesh_->pos_[overpoint_]);
      }
      else
      {
        ControlPoints_.erase(vui);
      }
      Redraw();
    } 
    leftButton_=false;
    break;
  case ZZZKEY_D:
    if (overpoint_!=-1)
    {
      ControlPoints_.erase(ControlPoints_.begin()+overpoint_);
      overpoint_=-1;
      Redraw();
    }
    leftButton_=false;
    break;
  case ZZZKEY_F:
    selectedpoint_=-1;
    leftButton_=false;
    break;
  case ZZZKEY_NOKEY:
    OneObjRenderer::OnLButtonUp(nFlags,x,y);
  }
}

void ControlRenderer::OnKeyUp(unsigned int nChar, unsigned int nRepCnt, unsigned int nFlags)
{
  keyDown_=ZZZKEY_NOKEY;
  overpoint_=-1;
  Redraw();
}

void ControlRenderer::OnSize(unsigned int nType, int cx, int cy)
{
  if (cx!=0 && cy!=0)
  {
    if (target_) target_->ChangeSize(cx,cy);
    if (depth_) depth_->Create(GL_DEPTH_COMPONENT32,cx,cy);
  }
  OneObjRenderer::OnSize(nType,cx,cy);
}

int ControlRenderer::FindNearest(int x,int y)
{
  Image3uc img;
  target_->TextureToImage(img);
  GLubyte *data=(GLubyte*)&(img.At(height_-y,x));
  if (data[0]!=255)
  {
    zuint id=data[0]*256*256+data[1]*256+data[2];
    zuint idx;
    for (idx=0;idx<mesh_group_end_.size();idx++)
      if (id<mesh_group_end_[idx]) break;
    if (idx>0) id-=mesh_group_end_[idx-1];
    Cartesian3DCoordf point[3];
    point[0]=mesh_->pos_[mesh_->groups_[idx]->facep_[id][0]];
    point[1]=mesh_->pos_[mesh_->groups_[idx]->facep_[id][1]];
    point[2]=mesh_->pos_[mesh_->groups_[idx]->facep_[id][2]];

    float mindist=1000;
    int minp=-1;
    double winx,winy,winz;
    gluProject(point[0][0],point[0][1],point[0][2],modelMatrix_,projMatrix_,viewport_,&winx,&winy,&winz);
    float dist=(Vector2f(x,height_-y)-Vector2f(winx,winy)).Len();
    if (mindist>dist) {mindist=dist;minp=0;}

    gluProject(point[1][0],point[1][1],point[1][2],modelMatrix_,projMatrix_,viewport_,&winx,&winy,&winz);
    dist=(Vector2f(x,height_-y)-Vector2f(winx,winy)).Len();
    if (mindist>dist) {mindist=dist;minp=1;}

    gluProject(point[2][0],point[2][1],point[2][2],modelMatrix_,projMatrix_,viewport_,&winx,&winy,&winz);
    dist=(Vector2f(x,height_-y)-Vector2f(winx,winy)).Len();
    if (mindist>dist) {mindist=dist;minp=0;}

    minp=mesh_->groups_[idx]->facep_[id][minp];
    return minp;
  }
  else
  {
    return -1;
  }
}

int ControlRenderer::FindNearestSelected(int x,int y)
{
  glMatrixMode(GL_MODELVIEW);
  glPushMatrix();
  glLoadIdentity();
  camera_.ApplyGL();
  
  objArcBall_.ApplyGL();

  GLdouble modelMatrix[16], projMatrix[16];
  GLint viewport[4];
  glGetDoublev(GL_MODELVIEW_MATRIX,modelMatrix);
  glGetDoublev(GL_PROJECTION_MATRIX,projMatrix);
  glGetIntegerv(GL_VIEWPORT,viewport);

  glPopMatrix();

  float mindist=1000;
  int minp=-1;
  for (zuint i=0; i<ControlPoints_.size(); i++)
  {
    Cartesian3DCoordf point;
    point=ControlPoints_[i];

    double winx,winy,winz;
    gluProject(point.v[0],point.v[1],point.v[2],modelMatrix_,projMatrix_,viewport_,&winx,&winy,&winz);
    float dist=(Vector2f(x,height_-y)-Vector2f(winx,winy)).Len();
    if (mindist>dist) {mindist=dist;minp=i;}

  }
  if (mindist<50)
  {
    return minp;
  }
  else
  {
    return -1;
  }
}

bool ControlRenderer::LoadObj(const string & filename)
{
  if (mesh_) delete mesh_;
  mesh_=new TriMesh;
  if (!ObjIO::Load(filename,*mesh_))
  {
    printf("Cannot open file.");
    delete mesh_;
    mesh_=NULL;
    return false;
  }
  //prepare id color
  mesh_group_end_.clear();
  mesh_group_end_.push_back(mesh_->groups_[0]->facep_.size());
  for (zuint i=1; i<mesh_->groups_.size(); i++)
    mesh_group_end_.push_back(mesh_group_end_[i-1]+mesh_->groups_[i]->facep_.size());


  int cur=0;
  for (zuint idx=0;idx<mesh_->groups_.size();idx++)
  {
    GLubyte *color=new GLubyte[mesh_group_end_[idx]*9];
    for (zuint i=0; i<mesh_->groups_[idx]->facep_.size(); i++)
    {
      zuint id=cur;
      GLubyte r,g,b;
      b=id%256;
      id-=b;
      id/=256;
      g=id%256;
      id-=g;
      id/=256;
      r=id;

      color[cur*9+0]=r;color[cur*9+1]=g;color[cur*9+2]=b;
      color[cur*9+3]=r;color[cur*9+4]=g;color[cur*9+5]=b;
      color[cur*9+6]=r;color[cur*9+7]=g;color[cur*9+8]=b;
      cur++;
    }
    mesh_->groups_[idx]->VBOColor_.Create(color,mesh_group_end_[idx]*3,VBODescript::Color3ub);
    delete[] color;
  }
  mesh_->CreateVBO(MESH_POS);
  mesh_->CalPosNormal();

  GLfloat lpos[4]={0,0,10,1};
  GLfloat lamb[4]={0.5,0.5,0.5,1};
  GLfloat ldif[4]={1,1,1,1};
  GLfloat lspe[4]={1,1,1,1};
  glEnable(GL_LIGHTING);
  glEnable(GL_LIGHT0);
  glLightfv(GL_LIGHT0,GL_POSITION,lpos);
  glLightfv(GL_LIGHT0,GL_AMBIENT,lamb);
  glLightfv(GL_LIGHT0,GL_DIFFUSE,ldif);
  glLightfv(GL_LIGHT0,GL_SPECULAR,lspe);
  glDisable(GL_LIGHTING);

  return true;
}

bool ControlRenderer::InitData()
{
  fbo_=new FBO;
  target_=new Texture2D3ub(1,1);
  depth_=new Renderbuffer(GL_DEPTH_COMPONENT32,1,1);
  fbo_->AttachRenderBuffer(depth_->GetID(),GL_DEPTH_ATTACHMENT_EXT);
  fbo_->AttachTexture(GL_TEXTURE_2D,target_->GetID(),GL_COLOR_ATTACHMENT0_EXT);
  fbo_->Disable();

  showMsg_=false;
  return OneObjRenderer::InitData();
}

void ControlRenderer::OnMouseMove(unsigned int nFlags,int x,int y)
{
  switch (keyDown_)
  {
  case ZZZKEY_S:
    overpoint_=FindNearest(x,y);
    Redraw();
    break;
  case ZZZKEY_D:
    if (!leftButton_)
    {
      overpoint_=FindNearestSelected(x,y);
      Redraw();
    }
    break;
  case ZZZKEY_F:
    if (!leftButton_)
    {
      overpoint_=FindNearestSelected(x,y);
      Redraw();
    }
    else
    {
      if (selectedpoint_!=-1)
      {
        glMatrixMode(GL_MODELVIEW);
        glPushMatrix();
        glLoadIdentity();
        camera_.ApplyGL();
        
        objArcBall_.ApplyGL();
        Cartesian3DCoordf point=ControlPoints_[selectedpoint_];

        GLdouble modelMatrix[16], projMatrix[16];
        GLint viewport[4];
        glGetDoublev(GL_MODELVIEW_MATRIX,modelMatrix);
        glGetDoublev(GL_PROJECTION_MATRIX,projMatrix);
        glGetIntegerv(GL_VIEWPORT,viewport);

        double objx,objy,objz;
        gluUnProject(selectedx_+x,selectedy_+height_-y,selectedz_,modelMatrix,projMatrix,viewport,&objx,&objy,&objz);
        ControlPoints_[selectedpoint_].v[0]=objx;
        ControlPoints_[selectedpoint_].v[1]=objy;
        ControlPoints_[selectedpoint_].v[2]=objz;

        glPopMatrix();

        Redraw();
      }
    }
    break;
  case ZZZKEY_NOKEY:
    OneObjRenderer::OnMouseMove(nFlags,x,y);
  }

}

void ControlRenderer::RenderIdMap()
{
  fbo_->Bind();
  glClearColor(1.0f,0.0f,0.0f,1.0f);
  glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT);

  glMatrixMode(GL_MODELVIEW);
  glPushMatrix();
  glLoadIdentity();
  camera_.ApplyGL();
  
  objArcBall_.ApplyGL();

  ZRM->Get<Shader*>("ColorShader")->Begin();
  mesh_->DrawVBO(MESH_POS|MESH_COLOR);
  Shader::End();

  glGetDoublev(GL_MODELVIEW_MATRIX,modelMatrix_);
  glGetDoublev(GL_PROJECTION_MATRIX,projMatrix_);
  glGetIntegerv(GL_VIEWPORT,viewport_);

  glPopMatrix();
  fbo_->Disable();
  glClearColor(0.1f,0.1f,0.1f,1.0f);


//  target_->TextureToImage();
}

}